/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.ListBox
{
  internal class ScrollingDownTransitionAnimation : TransitionAnimation
  {

    #region Constructor
    public ScrollingDownTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetThumbScrollingDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseOut;
      opacityEasing.Power = 2;

      PowerEase itemsRemovedEasing = new PowerEase();
      itemsRemovedEasing.EasingMode = EasingMode.EaseOut;
      itemsRemovedEasing.Power = 3;

      BackEase otherItemsEasing = new BackEase();
      otherItemsEasing.EasingMode = EasingMode.EaseIn;
      otherItemsEasing.Amplitude = 0.5;

      m_opacityEasing = opacityEasing;
      m_animatedItemsMovementEasing = itemsRemovedEasing;
      this.Orientation = Orientation.Vertical;
    }
    #endregion

    #region Orientation Property
    internal Orientation Orientation
    {
      get;
      set;
    }
    #endregion

    #region Internal Methods
    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    internal override void AddContainer( ListBoxContainer container )
    {
      if( !this.IsRunning )
        return;

      LayoutChangeInfo layoutChange = this.CreateLayoutChangeInfo();

      if( this.PrepareContainer( container, layoutChange ) )
      {
        //Update the element with start time, ensure that as soon as "start" completes, state if correct for all items.
        if( !this.UpdateContainer( 0d, container ) )
        {
          //If both preparation let pass the item and update did not filter it out, mark the item as managed by TransitionAnimation
          this.HandledItems.Add( container );
        }
      }
    }

    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> affectedElement )
    {
      IEnumerable<LayoutChangeInfo> changedElements = from kvp in affectedElement
                                                      let layoutChangeInfo = kvp.Value
                                                      let container = kvp.Key
                                                      where layoutChangeInfo.ChangeType == ChangeType.ElementChanged
                                                      select layoutChangeInfo;

      // If a changed element is part of the tarnsiton 
      if( changedElements.Count() > 0 )
      {
        // The scrollin to apply is the offset change of the changed element
        if( this.Orientation == Orientation.Vertical )
        {
          m_scrollingOffsetInPixels.X = double.NaN;
          m_scrollingOffsetInPixels.Y = changedElements.First().YChange;
        }
        else
        {
          m_scrollingOffsetInPixels.X = changedElements.First().XChange;
          m_scrollingOffsetInPixels.Y = double.NaN;
        }
      }
      else
      {

        IEnumerable<ListBoxContainer> removedElements = from kvp in affectedElement
                                                        let layoutChangeInfo = kvp.Value
                                                        let container = kvp.Key
                                                        where layoutChangeInfo.ChangeType == ChangeType.ElementRemoved
                                                        select container;

        IEnumerable<ListBoxContainer> addedElements = from kvp in affectedElement
                                                      let layoutChangeInfo = kvp.Value
                                                      let container = kvp.Key
                                                      where layoutChangeInfo.ChangeType == ChangeType.ElementAdded
                                                      select container;

        Rect addedOperationRect = this.ComputeOperationsRect( addedElements );
        Rect removedOperationRect = this.ComputeOperationsRect( removedElements );

        // Compute the scrolling offset to apply to all containers in the layout
        // to move from the original layout to the new layout
        double destination;
        double startPosition;

        if( this.Orientation == Orientation.Vertical )
        {
          destination = addedOperationRect.Y;
          startPosition = removedOperationRect.Y + removedOperationRect.Height;
          m_scrollingOffsetInPixels.X = double.NaN;
          m_scrollingOffsetInPixels.Y = startPosition - destination;
        }
        else
        {
          destination = addedOperationRect.X;
          startPosition = removedOperationRect.X + removedOperationRect.Width;
          m_scrollingOffsetInPixels.X = startPosition - destination;
          m_scrollingOffsetInPixels.Y = double.NaN;
        }
      }
    }

    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      if( changeInfo == null )
        throw new ListBoxInternalException();

      if( ( changeInfo.ChangeType == ChangeType.ElementAdded ) ||
          ( changeInfo.ChangeType == ChangeType.ElementChanged ) )
      {

        PositionAnimation positionAnimation = new PositionAnimation();

        ( ( IAnimation )positionAnimation ).ParentTransition = this;
        ( ( IAnimation )positionAnimation ).EasingFunction = m_animatedItemsMovementEasing;

        positionAnimation.XChange = m_scrollingOffsetInPixels.X;
        positionAnimation.YChange = m_scrollingOffsetInPixels.Y;

        container.AddAnimation( positionAnimation );

        return true;
      }
      else
      {
        // Changed Element == ElementRemoved

        PositionAnimation positionAnimation = new PositionAnimation();
        ( ( IAnimation )positionAnimation ).EasingFunction = m_animatedItemsMovementEasing;
        ( ( IAnimation )positionAnimation ).ParentTransition = this;
        ( ( IAnimation )positionAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;

        SizeAnimation sizeAnimation = new SizeAnimation();
        ( ( IAnimation )sizeAnimation ).EasingFunction = m_animatedItemsMovementEasing;
        ( ( IAnimation )sizeAnimation ).ParentTransition = this;

        positionAnimation.XChange = -m_scrollingOffsetInPixels.X;
        positionAnimation.YChange = -m_scrollingOffsetInPixels.Y;

        sizeAnimation.MaximumWidthChange = m_scrollingOffsetInPixels.X;
        sizeAnimation.MaximumHeightChange = m_scrollingOffsetInPixels.Y;
        sizeAnimation.WidthChange = double.NaN;
        sizeAnimation.HeightChange = double.NaN;

        container.AddAnimation( positionAnimation );
        container.AddAnimation( sizeAnimation );

        return true;
      }
    }

    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException( "container cannot be null." );

      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();
    }
    #endregion

    #region Private Methods
    private Rect ComputeOperationsRect( IEnumerable<ListBoxContainer> elements )
    {
      Rect operationRect = Rect.Empty;
      bool operationOccured = false;
      double lowestOperationX = double.MaxValue;
      double lowestOperationY = double.MaxValue;
      double highestOperationX = double.MinValue;
      double highestOperationY = double.MinValue;

      foreach( ListBoxContainer listBoxContainer in elements )
      {
        Point containerPosition = listBoxContainer.AbsoluteOffset;

        double elementHeight = listBoxContainer.AbsoluteElementHeight;
        double elementWidth = listBoxContainer.AbsoluteElementWidth;

        if( containerPosition.X < lowestOperationX )
        {
          lowestOperationX = containerPosition.X;
        }

        if( containerPosition.Y < lowestOperationY )
        {
          lowestOperationY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestOperationX )
        {
          highestOperationX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestOperationY )
        {
          highestOperationY = containerPosition.Y + elementHeight;
        }

        operationOccured = true;
      }

      if( operationOccured )
      {
        operationRect.X = lowestOperationX;
        operationRect.Y = lowestOperationY;
        operationRect.Width = highestOperationX - lowestOperationX;
        operationRect.Height = highestOperationY - lowestOperationY;
      }

      return operationRect;
    }

    #endregion

    #region Private Methods
    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_animatedItemsMovementEasing;
    private Point m_scrollingOffsetInPixels = new Point( 0.0d, 0.0d );
    #endregion

  }
}
